// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/appcache/appcache_storage_impl.h"

#include <stddef.h>

#include <algorithm>
#include <functional>
#include <limits>
#include <set>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/profiler/scoped_tracker.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/appcache/appcache.h"
#include "content/browser/appcache/appcache_database.h"
#include "content/browser/appcache/appcache_entry.h"
#include "content/browser/appcache/appcache_group.h"
#include "content/browser/appcache/appcache_histograms.h"
#include "content/browser/appcache/appcache_quota_client.h"
#include "content/browser/appcache/appcache_response.h"
#include "content/browser/appcache/appcache_service_impl.h"
#include "net/base/cache_type.h"
#include "net/base/net_errors.h"
#include "sql/connection.h"
#include "sql/transaction.h"
#include "storage/browser/quota/quota_client.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/special_storage_policy.h"

namespace content {

// Hard coded default when not using quota management.
static const int kDefaultQuota = 5 * 1024 * 1024;

static const int kMaxDiskCacheSize = 250 * 1024 * 1024;
static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
static const base::FilePath::CharType kDiskCacheDirectoryName[] = FILE_PATH_LITERAL("Cache");

namespace {

    // Helpers for clearing data from the AppCacheDatabase.
    bool DeleteGroupAndRelatedRecords(
        AppCacheDatabase* database,
        int64_t group_id,
        std::vector<int64_t>* deletable_response_ids)
    {
        AppCacheDatabase::CacheRecord cache_record;
        bool success = false;
        if (database->FindCacheForGroup(group_id, &cache_record)) {
            database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
                deletable_response_ids);
            success = database->DeleteGroup(group_id) && database->DeleteCache(cache_record.cache_id) && database->DeleteEntriesForCache(cache_record.cache_id) && database->DeleteNamespacesForCache(cache_record.cache_id) && database->DeleteOnlineWhiteListForCache(cache_record.cache_id) && database->InsertDeletableResponseIds(*deletable_response_ids);
        } else {
            NOTREACHED() << "A existing group without a cache is unexpected";
            success = database->DeleteGroup(group_id);
        }
        return success;
    }

    // Destroys |database|. If there is appcache data to be deleted
    // (|force_keep_session_state| is false), deletes session-only appcache data.
    void ClearSessionOnlyOrigins(
        AppCacheDatabase* database,
        scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
        bool force_keep_session_state)
    {
        std::unique_ptr<AppCacheDatabase> database_to_delete(database);

        // If saving session state, only delete the database.
        if (force_keep_session_state)
            return;

        bool has_session_only_appcaches = special_storage_policy.get() && special_storage_policy->HasSessionOnlyOrigins();

        // Clearning only session-only databases, and there are none.
        if (!has_session_only_appcaches)
            return;

        std::set<GURL> origins;
        database->FindOriginsWithGroups(&origins);
        if (origins.empty())
            return; // nothing to delete

        sql::Connection* connection = database->db_connection();
        if (!connection) {
            NOTREACHED() << "Missing database connection.";
            return;
        }

        std::set<GURL>::const_iterator origin;
        DCHECK(special_storage_policy.get());
        for (origin = origins.begin(); origin != origins.end(); ++origin) {
            if (!special_storage_policy->IsStorageSessionOnly(*origin))
                continue;
            if (special_storage_policy->IsStorageProtected(*origin))
                continue;

            std::vector<AppCacheDatabase::GroupRecord> groups;
            database->FindGroupsForOrigin(*origin, &groups);
            std::vector<AppCacheDatabase::GroupRecord>::const_iterator group;
            for (group = groups.begin(); group != groups.end(); ++group) {
                sql::Transaction transaction(connection);
                if (!transaction.Begin()) {
                    NOTREACHED() << "Failed to start transaction";
                    return;
                }
                std::vector<int64_t> deletable_response_ids;
                bool success = DeleteGroupAndRelatedRecords(database,
                    group->group_id,
                    &deletable_response_ids);
                success = success && transaction.Commit();
                DCHECK(success);
            } // for each group
        } // for each origin
    }

} // namespace

// DatabaseTask -----------------------------------------

class AppCacheStorageImpl::DatabaseTask
    : public base::RefCountedThreadSafe<DatabaseTask> {
public:
    explicit DatabaseTask(AppCacheStorageImpl* storage)
        : storage_(storage)
        , database_(storage->database_)
        , io_thread_(base::ThreadTaskRunnerHandle::Get())
    {
        DCHECK(io_thread_.get());
    }

    void AddDelegate(DelegateReference* delegate_reference)
    {
        delegates_.push_back(make_scoped_refptr(delegate_reference));
    }

    // Schedules a task to be Run() on the DB thread. Tasks
    // are run in the order in which they are scheduled.
    void Schedule();

    // Called on the DB thread.
    virtual void Run() = 0;

    // Called on the IO thread after Run() has completed.
    virtual void RunCompleted() { }

    // Once scheduled a task cannot be cancelled, but the
    // call to RunCompleted may be. This method should only be
    // called on the IO thread. This is used by AppCacheStorageImpl
    // to cancel the completion calls when AppCacheStorageImpl is
    // destructed. This method may be overriden to release or delete
    // additional data associated with the task that is not DB thread
    // safe. If overriden, this base class method must be called from
    // within the override.
    virtual void CancelCompletion();

protected:
    friend class base::RefCountedThreadSafe<DatabaseTask>;
    virtual ~DatabaseTask() { }

    AppCacheStorageImpl* storage_;
    AppCacheDatabase* database_;
    DelegateReferenceVector delegates_;

private:
    void CallRun(base::TimeTicks schedule_time);
    void CallRunCompleted(base::TimeTicks schedule_time);
    void OnFatalError();

    scoped_refptr<base::SingleThreadTaskRunner> io_thread_;
};

void AppCacheStorageImpl::DatabaseTask::Schedule()
{
    DCHECK(storage_);
    DCHECK(io_thread_->BelongsToCurrentThread());
    if (!storage_->database_)
        return;

    if (storage_->db_thread_->PostTask(
            FROM_HERE,
            base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) {
        storage_->scheduled_database_tasks_.push_back(this);
    } else {
        NOTREACHED() << "Thread for database tasks is not running.";
    }
}

void AppCacheStorageImpl::DatabaseTask::CancelCompletion()
{
    DCHECK(io_thread_->BelongsToCurrentThread());
    delegates_.clear();
    storage_ = NULL;
}

void AppCacheStorageImpl::DatabaseTask::CallRun(
    base::TimeTicks schedule_time)
{
    AppCacheHistograms::AddTaskQueueTimeSample(
        base::TimeTicks::Now() - schedule_time);
    if (!database_->is_disabled()) {
        base::TimeTicks run_time = base::TimeTicks::Now();
        Run();
        AppCacheHistograms::AddTaskRunTimeSample(
            base::TimeTicks::Now() - run_time);

        if (database_->was_corruption_detected()) {
            AppCacheHistograms::CountCorruptionDetected();
            database_->Disable();
        }
        if (database_->is_disabled()) {
            io_thread_->PostTask(
                FROM_HERE,
                base::Bind(&DatabaseTask::OnFatalError, this));
        }
    }
    io_thread_->PostTask(
        FROM_HERE,
        base::Bind(&DatabaseTask::CallRunCompleted, this,
            base::TimeTicks::Now()));
}

void AppCacheStorageImpl::DatabaseTask::CallRunCompleted(
    base::TimeTicks schedule_time)
{
    AppCacheHistograms::AddCompletionQueueTimeSample(
        base::TimeTicks::Now() - schedule_time);
    if (storage_) {
        DCHECK(io_thread_->BelongsToCurrentThread());
        DCHECK(storage_->scheduled_database_tasks_.front() == this);
        storage_->scheduled_database_tasks_.pop_front();
        base::TimeTicks run_time = base::TimeTicks::Now();
        RunCompleted();
        AppCacheHistograms::AddCompletionRunTimeSample(
            base::TimeTicks::Now() - run_time);
        delegates_.clear();
    }
}

void AppCacheStorageImpl::DatabaseTask::OnFatalError()
{
    if (storage_) {
        DCHECK(io_thread_->BelongsToCurrentThread());
        storage_->Disable();
        storage_->DeleteAndStartOver();
    }
}

// InitTask -------

class AppCacheStorageImpl::InitTask : public DatabaseTask {
public:
    explicit InitTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage)
        , last_group_id_(0)
        , last_cache_id_(0)
        , last_response_id_(0)
        , last_deletable_response_rowid_(0)
    {
        if (!storage->is_incognito_) {
            db_file_path_ = storage->cache_directory_.Append(kAppCacheDatabaseName);
            disk_cache_directory_ = storage->cache_directory_.Append(kDiskCacheDirectoryName);
        }
    }

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;

protected:
    ~InitTask() override { }

private:
    base::FilePath db_file_path_;
    base::FilePath disk_cache_directory_;
    int64_t last_group_id_;
    int64_t last_cache_id_;
    int64_t last_response_id_;
    int64_t last_deletable_response_rowid_;
    std::map<GURL, int64_t> usage_map_;
};

void AppCacheStorageImpl::InitTask::Run()
{
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::InitTask"));
    // If there is no sql database, ensure there is no disk cache either.
    if (!db_file_path_.empty() && !base::PathExists(db_file_path_) && base::DirectoryExists(disk_cache_directory_)) {
        base::DeleteFile(disk_cache_directory_, true);
        if (base::DirectoryExists(disk_cache_directory_)) {
            database_->Disable(); // This triggers OnFatalError handling.
            return;
        }
    }

    database_->FindLastStorageIds(
        &last_group_id_, &last_cache_id_, &last_response_id_,
        &last_deletable_response_rowid_);
    database_->GetAllOriginUsage(&usage_map_);
}

void AppCacheStorageImpl::InitTask::RunCompleted()
{
    storage_->last_group_id_ = last_group_id_;
    storage_->last_cache_id_ = last_cache_id_;
    storage_->last_response_id_ = last_response_id_;
    storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;

    if (!storage_->is_disabled()) {
        storage_->usage_map_.swap(usage_map_);
        const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
        base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
            FROM_HERE,
            base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
                storage_->weak_factory_.GetWeakPtr()),
            kDelay);
    }

    if (storage_->service()->quota_client())
        storage_->service()->quota_client()->NotifyAppCacheReady();
}

// DisableDatabaseTask -------

class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
public:
    explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage)
    {
    }

    // DatabaseTask:
    void Run() override { database_->Disable(); }

protected:
    ~DisableDatabaseTask() override { }
};

// GetAllInfoTask -------

class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
public:
    explicit GetAllInfoTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage)
        , info_collection_(new AppCacheInfoCollection())
    {
    }

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;

protected:
    ~GetAllInfoTask() override { }

private:
    scoped_refptr<AppCacheInfoCollection> info_collection_;
};

void AppCacheStorageImpl::GetAllInfoTask::Run()
{
    std::set<GURL> origins;
    database_->FindOriginsWithGroups(&origins);
    for (std::set<GURL>::const_iterator origin = origins.begin();
         origin != origins.end(); ++origin) {
        AppCacheInfoVector& infos = info_collection_->infos_by_origin[*origin];
        std::vector<AppCacheDatabase::GroupRecord> groups;
        database_->FindGroupsForOrigin(*origin, &groups);
        for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator
                 group
             = groups.begin();
             group != groups.end(); ++group) {
            AppCacheDatabase::CacheRecord cache_record;
            database_->FindCacheForGroup(group->group_id, &cache_record);
            AppCacheInfo info;
            info.manifest_url = group->manifest_url;
            info.creation_time = group->creation_time;
            info.size = cache_record.cache_size;
            info.last_access_time = group->last_access_time;
            info.last_update_time = cache_record.update_time;
            info.cache_id = cache_record.cache_id;
            info.group_id = group->group_id;
            info.is_complete = true;
            infos.push_back(info);
        }
    }
}

void AppCacheStorageImpl::GetAllInfoTask::RunCompleted()
{
    DCHECK_EQ(1U, delegates_.size());
    FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_.get()));
}

// StoreOrLoadTask -------

class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
protected:
    explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage)
    {
    }
    ~StoreOrLoadTask() override { }

    bool FindRelatedCacheRecords(int64_t cache_id);
    void CreateCacheAndGroupFromRecords(
        scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);

    AppCacheDatabase::GroupRecord group_record_;
    AppCacheDatabase::CacheRecord cache_record_;
    std::vector<AppCacheDatabase::EntryRecord> entry_records_;
    std::vector<AppCacheDatabase::NamespaceRecord>
        intercept_namespace_records_;
    std::vector<AppCacheDatabase::NamespaceRecord>
        fallback_namespace_records_;
    std::vector<AppCacheDatabase::OnlineWhiteListRecord>
        online_whitelist_records_;
};

bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
    int64_t cache_id)
{
    return database_->FindEntriesForCache(cache_id, &entry_records_) && database_->FindNamespacesForCache(cache_id, &intercept_namespace_records_, &fallback_namespace_records_) && database_->FindOnlineWhiteListForCache(cache_id, &online_whitelist_records_);
}

void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
    scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group)
{
    DCHECK(storage_ && cache && group);

    (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id);
    if (cache->get()) {
        (*group) = cache->get()->owning_group();
        DCHECK(group->get());
        DCHECK_EQ(group_record_.group_id, group->get()->group_id());

        // TODO(michaeln): histogram is fishing for clues to crbug/95101
        if (!cache->get()->GetEntry(group_record_.manifest_url)) {
            AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
                AppCacheHistograms::CALLSITE_0);
        }

        storage_->NotifyStorageAccessed(group_record_.origin);
        return;
    }

    (*cache) = new AppCache(storage_, cache_record_.cache_id);
    cache->get()->InitializeWithDatabaseRecords(
        cache_record_, entry_records_,
        intercept_namespace_records_,
        fallback_namespace_records_,
        online_whitelist_records_);
    cache->get()->set_complete(true);

    (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url);
    if (group->get()) {
        DCHECK(group_record_.group_id == group->get()->group_id());
        group->get()->AddCache(cache->get());

        // TODO(michaeln): histogram is fishing for clues to crbug/95101
        if (!cache->get()->GetEntry(group_record_.manifest_url)) {
            AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
                AppCacheHistograms::CALLSITE_1);
        }
    } else {
        (*group) = new AppCacheGroup(
            storage_, group_record_.manifest_url,
            group_record_.group_id);
        group->get()->set_creation_time(group_record_.creation_time);
        group->get()->set_last_full_update_check_time(
            group_record_.last_full_update_check_time);
        group->get()->set_first_evictable_error_time(
            group_record_.first_evictable_error_time);
        group->get()->AddCache(cache->get());

        // TODO(michaeln): histogram is fishing for clues to crbug/95101
        if (!cache->get()->GetEntry(group_record_.manifest_url)) {
            AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
                AppCacheHistograms::CALLSITE_2);
        }
    }
    DCHECK(group->get()->newest_complete_cache() == cache->get());

    // We have to update foriegn entries if MarkEntryAsForeignTasks
    // are in flight.
    std::vector<GURL> urls;
    storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls);
    for (std::vector<GURL>::iterator iter = urls.begin();
         iter != urls.end(); ++iter) {
        DCHECK(cache->get()->GetEntry(*iter));
        cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN);
    }

    storage_->NotifyStorageAccessed(group_record_.origin);

    // TODO(michaeln): Maybe verify that the responses we expect to exist
    // do actually exist in the disk_cache (and if not then what?)
}

// CacheLoadTask -------

class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
public:
    CacheLoadTask(int64_t cache_id, AppCacheStorageImpl* storage)
        : StoreOrLoadTask(storage)
        , cache_id_(cache_id)
        , success_(false)
    {
    }

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;

protected:
    ~CacheLoadTask() override { }

private:
    int64_t cache_id_;
    bool success_;
};

void AppCacheStorageImpl::CacheLoadTask::Run()
{
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::CacheLoadTask"));
    success_ = database_->FindCache(cache_id_, &cache_record_) && database_->FindGroup(cache_record_.group_id, &group_record_) && FindRelatedCacheRecords(cache_id_);

    if (success_)
        database_->LazyUpdateLastAccessTime(group_record_.group_id,
            base::Time::Now());
}

void AppCacheStorageImpl::CacheLoadTask::RunCompleted()
{
    storage_->pending_cache_loads_.erase(cache_id_);
    scoped_refptr<AppCache> cache;
    scoped_refptr<AppCacheGroup> group;
    if (success_ && !storage_->is_disabled()) {
        storage_->LazilyCommitLastAccessTimes();
        DCHECK(cache_record_.cache_id == cache_id_);
        CreateCacheAndGroupFromRecords(&cache, &group);
    }
    FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache.get(), cache_id_));
}

// GroupLoadTask -------

class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
public:
    GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
        : StoreOrLoadTask(storage)
        , manifest_url_(manifest_url)
        , success_(false)
    {
    }

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;

protected:
    ~GroupLoadTask() override { }

private:
    GURL manifest_url_;
    bool success_;
};

void AppCacheStorageImpl::GroupLoadTask::Run()
{
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION("AppCacheStorageImpl::GroupLoadTask"));
    success_ = database_->FindGroupForManifestUrl(manifest_url_, &group_record_) && database_->FindCacheForGroup(group_record_.group_id, &cache_record_) && FindRelatedCacheRecords(cache_record_.cache_id);

    if (success_)
        database_->LazyUpdateLastAccessTime(group_record_.group_id,
            base::Time::Now());
}

void AppCacheStorageImpl::GroupLoadTask::RunCompleted()
{
    storage_->pending_group_loads_.erase(manifest_url_);
    scoped_refptr<AppCacheGroup> group;
    scoped_refptr<AppCache> cache;
    if (!storage_->is_disabled()) {
        if (success_) {
            storage_->LazilyCommitLastAccessTimes();
            DCHECK(group_record_.manifest_url == manifest_url_);
            CreateCacheAndGroupFromRecords(&cache, &group);
        } else {
            group = storage_->working_set_.GetGroup(manifest_url_);
            if (!group.get()) {
                group = new AppCacheGroup(storage_, manifest_url_, storage_->NewGroupId());
            }
        }
    }
    FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group.get(), manifest_url_));
}

// StoreGroupAndCacheTask -------

class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
public:
    StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
        AppCache* newest_cache);

    void GetQuotaThenSchedule();
    void OnQuotaCallback(storage::QuotaStatusCode status,
        int64_t usage,
        int64_t quota);

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;
    void CancelCompletion() override;

protected:
    ~StoreGroupAndCacheTask() override { }

private:
    scoped_refptr<AppCacheGroup> group_;
    scoped_refptr<AppCache> cache_;
    bool success_;
    bool would_exceed_quota_;
    int64_t space_available_;
    int64_t new_origin_usage_;
    std::vector<int64_t> newly_deletable_response_ids_;
};

AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
    AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache)
    : StoreOrLoadTask(storage)
    , group_(group)
    , cache_(newest_cache)
    , success_(false)
    , would_exceed_quota_(false)
    , space_available_(-1)
    , new_origin_usage_(-1)
{
    group_record_.group_id = group->group_id();
    group_record_.manifest_url = group->manifest_url();
    group_record_.origin = group_record_.manifest_url.GetOrigin();
    group_record_.last_full_update_check_time = group->last_full_update_check_time();
    group_record_.first_evictable_error_time = group->first_evictable_error_time();
    newest_cache->ToDatabaseRecords(
        group,
        &cache_record_, &entry_records_,
        &intercept_namespace_records_,
        &fallback_namespace_records_,
        &online_whitelist_records_);
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule()
{
    storage::QuotaManager* quota_manager = NULL;
    if (storage_->service()->quota_manager_proxy()) {
        quota_manager = storage_->service()->quota_manager_proxy()->quota_manager();
    }

    if (!quota_manager) {
        if (storage_->service()->special_storage_policy() && storage_->service()->special_storage_policy()->IsStorageUnlimited(group_record_.origin))
            space_available_ = std::numeric_limits<int64_t>::max();
        Schedule();
        return;
    }

    // crbug.com/349708
    TRACE_EVENT0(
        "io",
        "AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule");

    // We have to ask the quota manager for the value.
    storage_->pending_quota_queries_.insert(this);
    quota_manager->GetUsageAndQuota(
        group_record_.origin,
        storage::kStorageTypeTemporary,
        base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this));
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
    storage::QuotaStatusCode status,
    int64_t usage,
    int64_t quota)
{
    if (storage_) {
        if (status == storage::kQuotaStatusOk)
            space_available_ = std::max(static_cast<int64_t>(0), quota - usage);
        else
            space_available_ = 0;
        storage_->pending_quota_queries_.erase(this);
        Schedule();
    }
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::Run()
{
    DCHECK(!success_);
    sql::Connection* connection = database_->db_connection();
    if (!connection)
        return;

    sql::Transaction transaction(connection);
    if (!transaction.Begin())
        return;

    int64_t old_origin_usage = database_->GetOriginUsage(group_record_.origin);

    AppCacheDatabase::GroupRecord existing_group;
    success_ = database_->FindGroup(group_record_.group_id, &existing_group);
    if (!success_) {
        group_record_.creation_time = base::Time::Now();
        group_record_.last_access_time = base::Time::Now();
        success_ = database_->InsertGroup(&group_record_);
    } else {
        DCHECK(group_record_.group_id == existing_group.group_id);
        DCHECK(group_record_.manifest_url == existing_group.manifest_url);
        DCHECK(group_record_.origin == existing_group.origin);

        database_->UpdateLastAccessTime(group_record_.group_id,
            base::Time::Now());

        database_->UpdateEvictionTimes(
            group_record_.group_id,
            group_record_.last_full_update_check_time,
            group_record_.first_evictable_error_time);

        AppCacheDatabase::CacheRecord cache;
        if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
            // Get the set of response ids in the old cache.
            std::set<int64_t> existing_response_ids;
            database_->FindResponseIdsForCacheAsSet(cache.cache_id,
                &existing_response_ids);

            // Remove those that remain in the new cache.
            std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter = entry_records_.begin();
            while (entry_iter != entry_records_.end()) {
                existing_response_ids.erase(entry_iter->response_id);
                ++entry_iter;
            }

            // The rest are deletable.
            std::set<int64_t>::const_iterator id_iter = existing_response_ids.begin();
            while (id_iter != existing_response_ids.end()) {
                newly_deletable_response_ids_.push_back(*id_iter);
                ++id_iter;
            }

            success_ = database_->DeleteCache(cache.cache_id) && database_->DeleteEntriesForCache(cache.cache_id) && database_->DeleteNamespacesForCache(cache.cache_id) && database_->DeleteOnlineWhiteListForCache(cache.cache_id) && database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
            // TODO(michaeln): store group_id too with deletable ids
        } else {
            NOTREACHED() << "A existing group without a cache is unexpected";
        }
    }

    success_ = success_ && database_->InsertCache(&cache_record_) && database_->InsertEntryRecords(entry_records_) && database_->InsertNamespaceRecords(intercept_namespace_records_) && database_->InsertNamespaceRecords(fallback_namespace_records_) && database_->InsertOnlineWhiteListRecords(online_whitelist_records_);

    if (!success_)
        return;

    new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);

    // Only check quota when the new usage exceeds the old usage.
    if (new_origin_usage_ <= old_origin_usage) {
        success_ = transaction.Commit();
        return;
    }

    // Use a simple hard-coded value when not using quota management.
    if (space_available_ == -1) {
        if (new_origin_usage_ > kDefaultQuota) {
            would_exceed_quota_ = true;
            success_ = false;
            return;
        }
        success_ = transaction.Commit();
        return;
    }

    // Check limits based on the space availbable given to us via the
    // quota system.
    int64_t delta = new_origin_usage_ - old_origin_usage;
    if (delta > space_available_) {
        would_exceed_quota_ = true;
        success_ = false;
        return;
    }

    success_ = transaction.Commit();
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted()
{
    if (success_) {
        storage_->UpdateUsageMapAndNotify(
            group_->manifest_url().GetOrigin(), new_origin_usage_);
        if (cache_.get() != group_->newest_complete_cache()) {
            cache_->set_complete(true);
            group_->AddCache(cache_.get());
        }
        if (group_->creation_time().is_null())
            group_->set_creation_time(group_record_.creation_time);
        group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
    }
    FOR_EACH_DELEGATE(
        delegates_,
        OnGroupAndNewestCacheStored(
            group_.get(), cache_.get(), success_, would_exceed_quota_));
    group_ = NULL;
    cache_ = NULL;

    // TODO(michaeln): if (would_exceed_quota_) what if the current usage
    // also exceeds the quota? http://crbug.com/83968
}

void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion()
{
    // Overriden to safely drop our reference to the group and cache
    // which are not thread safe refcounted.
    DatabaseTask::CancelCompletion();
    group_ = NULL;
    cache_ = NULL;
}

// FindMainResponseTask -------

// Helpers for FindMainResponseTask::Run()
namespace {
    class SortByCachePreference {
    public:
        SortByCachePreference(int64_t preferred_id,
            const std::set<int64_t>& in_use_ids)
            : preferred_id_(preferred_id)
            , in_use_ids_(in_use_ids)
        {
        }
        bool operator()(
            const AppCacheDatabase::EntryRecord& lhs,
            const AppCacheDatabase::EntryRecord& rhs)
        {
            return compute_value(lhs) > compute_value(rhs);
        }

    private:
        int compute_value(const AppCacheDatabase::EntryRecord& entry)
        {
            if (entry.cache_id == preferred_id_)
                return 100;
            else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
                return 50;
            return 0;
        }
        int64_t preferred_id_;
        const std::set<int64_t>& in_use_ids_;
    };

    bool SortByLength(
        const AppCacheDatabase::NamespaceRecord& lhs,
        const AppCacheDatabase::NamespaceRecord& rhs)
    {
        return lhs.namespace_.namespace_url.spec().length() > rhs.namespace_.namespace_url.spec().length();
    }

    class NetworkNamespaceHelper {
    public:
        explicit NetworkNamespaceHelper(AppCacheDatabase* database)
            : database_(database)
        {
        }

        bool IsInNetworkNamespace(const GURL& url, int64_t cache_id)
        {
            typedef std::pair<WhiteListMap::iterator, bool> InsertResult;
            InsertResult result = namespaces_map_.insert(
                WhiteListMap::value_type(cache_id, AppCacheNamespaceVector()));
            if (result.second)
                GetOnlineWhiteListForCache(cache_id, &result.first->second);
            return AppCache::FindNamespace(result.first->second, url) != NULL;
        }

    private:
        void GetOnlineWhiteListForCache(int64_t cache_id,
            AppCacheNamespaceVector* namespaces)
        {
            DCHECK(namespaces && namespaces->empty());
            typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord>
                WhiteListVector;
            WhiteListVector records;
            if (!database_->FindOnlineWhiteListForCache(cache_id, &records))
                return;
            WhiteListVector::const_iterator iter = records.begin();
            while (iter != records.end()) {
                namespaces->push_back(
                    AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, iter->namespace_url,
                        GURL(), iter->is_pattern));
                ++iter;
            }
        }

        // Key is cache id
        typedef std::map<int64_t, AppCacheNamespaceVector> WhiteListMap;
        WhiteListMap namespaces_map_;
        AppCacheDatabase* database_;
    };

} // namespace

class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
public:
    FindMainResponseTask(AppCacheStorageImpl* storage,
        const GURL& url,
        const GURL& preferred_manifest_url,
        const AppCacheWorkingSet::GroupMap* groups_in_use)
        : DatabaseTask(storage)
        , url_(url)
        , preferred_manifest_url_(preferred_manifest_url)
        , cache_id_(kAppCacheNoCacheId)
        , group_id_(0)
    {
        if (groups_in_use) {
            for (AppCacheWorkingSet::GroupMap::const_iterator it = groups_in_use->begin();
                 it != groups_in_use->end(); ++it) {
                AppCacheGroup* group = it->second;
                AppCache* cache = group->newest_complete_cache();
                if (group->is_obsolete() || !cache)
                    continue;
                cache_ids_in_use_.insert(cache->cache_id());
            }
        }
    }

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;

protected:
    ~FindMainResponseTask() override { }

private:
    typedef std::vector<AppCacheDatabase::NamespaceRecord*>
        NamespaceRecordPtrVector;

    bool FindExactMatch(int64_t preferred_id);
    bool FindNamespaceMatch(int64_t preferred_id);
    bool FindNamespaceHelper(int64_t preferred_cache_id,
        AppCacheDatabase::NamespaceRecordVector* namespaces,
        NetworkNamespaceHelper* network_namespace_helper);
    bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);

    GURL url_;
    GURL preferred_manifest_url_;
    std::set<int64_t> cache_ids_in_use_;
    AppCacheEntry entry_;
    AppCacheEntry fallback_entry_;
    GURL namespace_entry_url_;
    int64_t cache_id_;
    int64_t group_id_;
    GURL manifest_url_;
};

void AppCacheStorageImpl::FindMainResponseTask::Run()
{
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION(
            "AppCacheStorageImpl::FindMainResponseTask"));
    // NOTE: The heuristics around choosing amoungst multiple candidates
    // is underspecified, and just plain not fully understood. This needs
    // to be refined.

    // The 'preferred_manifest_url' is the url of the manifest associated
    // with the page that opened or embedded the page being loaded now.
    // We have a strong preference to use resources from that cache.
    // We also have a lesser bias to use resources from caches that are currently
    // being used by other unrelated pages.
    // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
    // - when navigating a frame whose current contents are from an appcache
    // - when clicking an href in a frame that is appcached
    int64_t preferred_cache_id = kAppCacheNoCacheId;
    if (!preferred_manifest_url_.is_empty()) {
        AppCacheDatabase::GroupRecord preferred_group;
        AppCacheDatabase::CacheRecord preferred_cache;
        if (database_->FindGroupForManifestUrl(
                preferred_manifest_url_, &preferred_group)
            && database_->FindCacheForGroup(
                preferred_group.group_id, &preferred_cache)) {
            preferred_cache_id = preferred_cache.cache_id;
        }
    }

    if (FindExactMatch(preferred_cache_id) || FindNamespaceMatch(preferred_cache_id)) {
        // We found something.
        DCHECK(cache_id_ != kAppCacheNoCacheId && !manifest_url_.is_empty() && group_id_ != 0);
        return;
    }

    // We didn't find anything.
    DCHECK(cache_id_ == kAppCacheNoCacheId && manifest_url_.is_empty() && group_id_ == 0);
}

bool AppCacheStorageImpl::FindMainResponseTask::FindExactMatch(
    int64_t preferred_cache_id)
{
    std::vector<AppCacheDatabase::EntryRecord> entries;
    if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
        // Sort them in order of preference, from the preferred_cache first,
        // followed by hits from caches that are 'in use', then the rest.
        std::sort(entries.begin(), entries.end(),
            SortByCachePreference(preferred_cache_id, cache_ids_in_use_));

        // Take the first with a valid, non-foreign entry.
        std::vector<AppCacheDatabase::EntryRecord>::iterator iter;
        for (iter = entries.begin(); iter < entries.end(); ++iter) {
            AppCacheDatabase::GroupRecord group_record;
            if ((iter->flags & AppCacheEntry::FOREIGN) || !database_->FindGroupForCache(iter->cache_id, &group_record)) {
                continue;
            }
            manifest_url_ = group_record.manifest_url;
            group_id_ = group_record.group_id;
            entry_ = AppCacheEntry(iter->flags, iter->response_id);
            cache_id_ = iter->cache_id;
            return true; // We found an exact match.
        }
    }
    return false;
}

bool AppCacheStorageImpl::FindMainResponseTask::FindNamespaceMatch(
    int64_t preferred_cache_id)
{
    AppCacheDatabase::NamespaceRecordVector all_intercepts;
    AppCacheDatabase::NamespaceRecordVector all_fallbacks;
    if (!database_->FindNamespacesForOrigin(
            url_.GetOrigin(), &all_intercepts, &all_fallbacks)
        || (all_intercepts.empty() && all_fallbacks.empty())) {
        return false;
    }

    NetworkNamespaceHelper network_namespace_helper(database_);
    if (FindNamespaceHelper(preferred_cache_id,
            &all_intercepts,
            &network_namespace_helper)
        || FindNamespaceHelper(preferred_cache_id,
            &all_fallbacks,
            &network_namespace_helper)) {
        return true;
    }
    return false;
}

bool AppCacheStorageImpl::FindMainResponseTask::FindNamespaceHelper(
    int64_t preferred_cache_id,
    AppCacheDatabase::NamespaceRecordVector* namespaces,
    NetworkNamespaceHelper* network_namespace_helper)
{
    // Sort them by length, longer matches within the same cache/bucket take
    // precedence.
    std::sort(namespaces->begin(), namespaces->end(), SortByLength);

    NamespaceRecordPtrVector preferred_namespaces;
    NamespaceRecordPtrVector inuse_namespaces;
    NamespaceRecordPtrVector other_namespaces;
    std::vector<AppCacheDatabase::NamespaceRecord>::iterator iter;
    for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) {
        // Skip those that aren't a match.
        if (!iter->namespace_.IsMatch(url_))
            continue;

        // Skip namespaces where the requested url falls into a network
        // namespace of its containing appcache.
        if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id))
            continue;

        // Bin them into one of our three buckets.
        if (iter->cache_id == preferred_cache_id)
            preferred_namespaces.push_back(&(*iter));
        else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end())
            inuse_namespaces.push_back(&(*iter));
        else
            other_namespaces.push_back(&(*iter));
    }

    if (FindFirstValidNamespace(preferred_namespaces) || FindFirstValidNamespace(inuse_namespaces) || FindFirstValidNamespace(other_namespaces))
        return true; // We found one.

    // We didn't find anything.
    return false;
}

bool AppCacheStorageImpl::
    FindMainResponseTask::FindFirstValidNamespace(
        const NamespaceRecordPtrVector& namespaces)
{
    // Take the first with a valid, non-foreign entry.
    NamespaceRecordPtrVector::const_iterator iter;
    for (iter = namespaces.begin(); iter < namespaces.end(); ++iter) {
        AppCacheDatabase::EntryRecord entry_record;
        if (database_->FindEntry((*iter)->cache_id, (*iter)->namespace_.target_url,
                &entry_record)) {
            AppCacheDatabase::GroupRecord group_record;
            if ((entry_record.flags & AppCacheEntry::FOREIGN) || !database_->FindGroupForCache(entry_record.cache_id, &group_record)) {
                continue;
            }
            manifest_url_ = group_record.manifest_url;
            group_id_ = group_record.group_id;
            cache_id_ = (*iter)->cache_id;
            namespace_entry_url_ = (*iter)->namespace_.target_url;
            if ((*iter)->namespace_.type == APPCACHE_FALLBACK_NAMESPACE)
                fallback_entry_ = AppCacheEntry(entry_record.flags,
                    entry_record.response_id);
            else
                entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id);
            return true; // We found one.
        }
    }
    return false; // We didn't find a match.
}

void AppCacheStorageImpl::FindMainResponseTask::RunCompleted()
{
    storage_->CallOnMainResponseFound(
        &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_,
        cache_id_, group_id_, manifest_url_);
}

// MarkEntryAsForeignTask -------

class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
public:
    MarkEntryAsForeignTask(AppCacheStorageImpl* storage,
        const GURL& url,
        int64_t cache_id)
        : DatabaseTask(storage)
        , cache_id_(cache_id)
        , entry_url_(url)
    {
    }

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;

protected:
    ~MarkEntryAsForeignTask() override { }

private:
    int64_t cache_id_;
    GURL entry_url_;
};

void AppCacheStorageImpl::MarkEntryAsForeignTask::Run()
{
    database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
}

void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted()
{
    DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ && storage_->pending_foreign_markings_.front().second == cache_id_);
    storage_->pending_foreign_markings_.pop_front();
}

// MakeGroupObsoleteTask -------

class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
public:
    MakeGroupObsoleteTask(AppCacheStorageImpl* storage,
        AppCacheGroup* group,
        int response_code);

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;
    void CancelCompletion() override;

protected:
    ~MakeGroupObsoleteTask() override { }

private:
    scoped_refptr<AppCacheGroup> group_;
    int64_t group_id_;
    GURL origin_;
    bool success_;
    int response_code_;
    int64_t new_origin_usage_;
    std::vector<int64_t> newly_deletable_response_ids_;
};

AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
    AppCacheStorageImpl* storage,
    AppCacheGroup* group,
    int response_code)
    : DatabaseTask(storage)
    , group_(group)
    , group_id_(group->group_id())
    , origin_(group->manifest_url().GetOrigin())
    , success_(false)
    , response_code_(response_code)
    , new_origin_usage_(-1)
{
}

void AppCacheStorageImpl::MakeGroupObsoleteTask::Run()
{
    DCHECK(!success_);
    sql::Connection* connection = database_->db_connection();
    if (!connection)
        return;

    sql::Transaction transaction(connection);
    if (!transaction.Begin())
        return;

    AppCacheDatabase::GroupRecord group_record;
    if (!database_->FindGroup(group_id_, &group_record)) {
        // This group doesn't exists in the database, nothing todo here.
        new_origin_usage_ = database_->GetOriginUsage(origin_);
        success_ = true;
        return;
    }

    DCHECK_EQ(group_record.origin, origin_);
    success_ = DeleteGroupAndRelatedRecords(database_,
        group_id_,
        &newly_deletable_response_ids_);

    new_origin_usage_ = database_->GetOriginUsage(origin_);
    success_ = success_ && transaction.Commit();
}

void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted()
{
    if (success_) {
        group_->set_obsolete(true);
        if (!storage_->is_disabled()) {
            storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
            group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);

            // Also remove from the working set, caches for an 'obsolete' group
            // may linger in use, but the group itself cannot be looked up by
            // 'manifest_url' in the working set any longer.
            storage_->working_set()->RemoveGroup(group_.get());
        }
    }
    FOR_EACH_DELEGATE(
        delegates_, OnGroupMadeObsolete(group_.get(), success_, response_code_));
    group_ = NULL;
}

void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion()
{
    // Overriden to safely drop our reference to the group
    // which is not thread safe refcounted.
    DatabaseTask::CancelCompletion();
    group_ = NULL;
}

// GetDeletableResponseIdsTask -------

class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
public:
    GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64_t max_rowid)
        : DatabaseTask(storage)
        , max_rowid_(max_rowid)
    {
    }

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;

protected:
    ~GetDeletableResponseIdsTask() override { }

private:
    int64_t max_rowid_;
    std::vector<int64_t> response_ids_;
};

void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run()
{
    const int kSqlLimit = 1000;
    database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
    // TODO(michaeln): retrieve group_ids too
}

void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted()
{
    if (!response_ids_.empty())
        storage_->StartDeletingResponses(response_ids_);
}

// InsertDeletableResponseIdsTask -------

class AppCacheStorageImpl::InsertDeletableResponseIdsTask
    : public DatabaseTask {
public:
    explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage)
    {
    }

    // DatabaseTask:
    void Run() override;

    std::vector<int64_t> response_ids_;

protected:
    ~InsertDeletableResponseIdsTask() override { }
};

void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run()
{
    database_->InsertDeletableResponseIds(response_ids_);
    // TODO(michaeln): store group_ids too
}

// DeleteDeletableResponseIdsTask -------

class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
    : public DatabaseTask {
public:
    explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage)
    {
    }

    // DatabaseTask:
    void Run() override;

    std::vector<int64_t> response_ids_;

protected:
    ~DeleteDeletableResponseIdsTask() override { }
};

void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run()
{
    database_->DeleteDeletableResponseIds(response_ids_);
}

// LazyUpdateLastAccessTimeTask -------

class AppCacheStorageImpl::LazyUpdateLastAccessTimeTask
    : public DatabaseTask {
public:
    LazyUpdateLastAccessTimeTask(
        AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
        : DatabaseTask(storage)
        , group_id_(group->group_id())
        , last_access_time_(time)
    {
        storage->NotifyStorageAccessed(group->manifest_url().GetOrigin());
    }

    // DatabaseTask:
    void Run() override;
    void RunCompleted() override;

protected:
    ~LazyUpdateLastAccessTimeTask() override { }

private:
    int64_t group_id_;
    base::Time last_access_time_;
};

void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::Run()
{
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION(
            "AppCacheStorageImpl::LazyUpdateLastAccessTimeTask"));
    database_->LazyUpdateLastAccessTime(group_id_, last_access_time_);
}

void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::RunCompleted()
{
    storage_->LazilyCommitLastAccessTimes();
}

// CommitLastAccessTimesTask -------

class AppCacheStorageImpl::CommitLastAccessTimesTask
    : public DatabaseTask {
public:
    CommitLastAccessTimesTask(AppCacheStorageImpl* storage)
        : DatabaseTask(storage)
    {
    }

    // DatabaseTask:
    void Run() override
    {
        tracked_objects::ScopedTracker tracking_profile(
            FROM_HERE_WITH_EXPLICIT_FUNCTION(
                "AppCacheStorageImpl::CommitLastAccessTimesTask"));
        database_->CommitLazyLastAccessTimes();
    }

protected:
    ~CommitLastAccessTimesTask() override { }
};

// UpdateEvictionTimes -------

class AppCacheStorageImpl::UpdateEvictionTimesTask
    : public DatabaseTask {
public:
    UpdateEvictionTimesTask(
        AppCacheStorageImpl* storage, AppCacheGroup* group)
        : DatabaseTask(storage)
        , group_id_(group->group_id())
        , last_full_update_check_time_(group->last_full_update_check_time())
        , first_evictable_error_time_(group->first_evictable_error_time())
    {
    }

    // DatabaseTask:
    void Run() override;

protected:
    ~UpdateEvictionTimesTask() override { }

private:
    int64_t group_id_;
    base::Time last_full_update_check_time_;
    base::Time first_evictable_error_time_;
};

void AppCacheStorageImpl::UpdateEvictionTimesTask::Run()
{
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION(
            "AppCacheStorageImpl::UpdateEvictionTimes"));
    database_->UpdateEvictionTimes(group_id_,
        last_full_update_check_time_,
        first_evictable_error_time_);
}

// AppCacheStorageImpl ---------------------------------------------------

AppCacheStorageImpl::AppCacheStorageImpl(AppCacheServiceImpl* service)
    : AppCacheStorage(service)
    , is_incognito_(false)
    , is_response_deletion_scheduled_(false)
    , did_start_deleting_responses_(false)
    , last_deletable_response_rowid_(0)
    , database_(NULL)
    , is_disabled_(false)
    , weak_factory_(this)
{
}

AppCacheStorageImpl::~AppCacheStorageImpl()
{
    for (auto* task : pending_quota_queries_)
        task->CancelCompletion();
    for (auto* task : scheduled_database_tasks_)
        task->CancelCompletion();

    if (database_ && !db_thread_->PostTask(FROM_HERE, base::Bind(&ClearSessionOnlyOrigins, database_, make_scoped_refptr(service_->special_storage_policy()), service()->force_keep_session_state()))) {
        delete database_;
    }
    database_ = NULL; // So no further database tasks can be scheduled.
}

void AppCacheStorageImpl::Initialize(
    const base::FilePath& cache_directory,
    const scoped_refptr<base::SingleThreadTaskRunner>& db_thread,
    const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread)
{
    DCHECK(db_thread.get());

    cache_directory_ = cache_directory;
    is_incognito_ = cache_directory_.empty();

    base::FilePath db_file_path;
    if (!is_incognito_)
        db_file_path = cache_directory_.Append(kAppCacheDatabaseName);
    database_ = new AppCacheDatabase(db_file_path);

    db_thread_ = db_thread;
    cache_thread_ = cache_thread;

    scoped_refptr<InitTask> task(new InitTask(this));
    task->Schedule();
}

void AppCacheStorageImpl::Disable()
{
    if (is_disabled_)
        return;
    VLOG(1) << "Disabling appcache storage.";
    is_disabled_ = true;
    ClearUsageMapAndNotify();
    working_set()->Disable();
    if (disk_cache_)
        disk_cache_->Disable();
    scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this));
    task->Schedule();
}

void AppCacheStorageImpl::GetAllInfo(Delegate* delegate)
{
    DCHECK(delegate);
    scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this));
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    task->Schedule();
}

void AppCacheStorageImpl::LoadCache(int64_t id, Delegate* delegate)
{
    DCHECK(delegate);
    if (is_disabled_) {
        delegate->OnCacheLoaded(NULL, id);
        return;
    }

    AppCache* cache = working_set_.GetCache(id);
    if (cache) {
        delegate->OnCacheLoaded(cache, id);
        if (cache->owning_group()) {
            scoped_refptr<DatabaseTask> update_task(
                new LazyUpdateLastAccessTimeTask(
                    this, cache->owning_group(), base::Time::Now()));
            update_task->Schedule();
        }
        return;
    }
    scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
    if (task.get()) {
        task->AddDelegate(GetOrCreateDelegateReference(delegate));
        return;
    }
    task = new CacheLoadTask(id, this);
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    task->Schedule();
    pending_cache_loads_[id] = task.get();
}

void AppCacheStorageImpl::LoadOrCreateGroup(
    const GURL& manifest_url, Delegate* delegate)
{
    DCHECK(delegate);
    if (is_disabled_) {
        delegate->OnGroupLoaded(NULL, manifest_url);
        return;
    }

    AppCacheGroup* group = working_set_.GetGroup(manifest_url);
    if (group) {
        delegate->OnGroupLoaded(group, manifest_url);
        scoped_refptr<DatabaseTask> update_task(
            new LazyUpdateLastAccessTimeTask(
                this, group, base::Time::Now()));
        update_task->Schedule();
        return;
    }

    scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
    if (task.get()) {
        task->AddDelegate(GetOrCreateDelegateReference(delegate));
        return;
    }

    if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) {
        // No need to query the database, return a new group immediately.
        scoped_refptr<AppCacheGroup> group(new AppCacheGroup(
            this, manifest_url, NewGroupId()));
        delegate->OnGroupLoaded(group.get(), manifest_url);
        return;
    }

    task = new GroupLoadTask(manifest_url, this);
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    task->Schedule();
    pending_group_loads_[manifest_url] = task.get();
}

void AppCacheStorageImpl::StoreGroupAndNewestCache(
    AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate)
{
    // TODO(michaeln): distinguish between a simple update of an existing
    // cache that just adds new master entry(s), and the insertion of a
    // whole new cache. The StoreGroupAndCacheTask as written will handle
    // the simple update case in a very heavy weight way (delete all and
    // the reinsert all over again).
    DCHECK(group && delegate && newest_cache);
    scoped_refptr<StoreGroupAndCacheTask> task(
        new StoreGroupAndCacheTask(this, group, newest_cache));
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    task->GetQuotaThenSchedule();

    // TODO(michaeln): histogram is fishing for clues to crbug/95101
    if (!newest_cache->GetEntry(group->manifest_url())) {
        AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
            AppCacheHistograms::CALLSITE_3);
    }
}

void AppCacheStorageImpl::FindResponseForMainRequest(
    const GURL& url, const GURL& preferred_manifest_url,
    Delegate* delegate)
{
    DCHECK(delegate);

    const GURL* url_ptr = &url;
    GURL url_no_ref;
    if (url.has_ref()) {
        GURL::Replacements replacements;
        replacements.ClearRef();
        url_no_ref = url.ReplaceComponents(replacements);
        url_ptr = &url_no_ref;
    }

    const GURL origin = url.GetOrigin();

    // First look in our working set for a direct hit without having to query
    // the database.
    const AppCacheWorkingSet::GroupMap* groups_in_use = working_set()->GetGroupsInOrigin(origin);
    if (groups_in_use) {
        if (!preferred_manifest_url.is_empty()) {
            AppCacheWorkingSet::GroupMap::const_iterator found = groups_in_use->find(preferred_manifest_url);
            if (found != groups_in_use->end() && FindResponseForMainRequestInGroup(found->second, *url_ptr, delegate)) {
                return;
            }
        } else {
            for (AppCacheWorkingSet::GroupMap::const_iterator it = groups_in_use->begin();
                 it != groups_in_use->end(); ++it) {
                if (FindResponseForMainRequestInGroup(
                        it->second, *url_ptr, delegate)) {
                    return;
                }
            }
        }
    }

    if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) {
        // No need to query the database, return async'ly but without going thru
        // the DB thread.
        scoped_refptr<AppCacheGroup> no_group;
        scoped_refptr<AppCache> no_cache;
        ScheduleSimpleTask(
            base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
                weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group,
                no_cache,
                make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
        return;
    }

    // We have to query the database, schedule a database task to do so.
    scoped_refptr<FindMainResponseTask> task(
        new FindMainResponseTask(this, *url_ptr, preferred_manifest_url,
            groups_in_use));
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    task->Schedule();
}

bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
    AppCacheGroup* group, const GURL& url, Delegate* delegate)
{
    AppCache* cache = group->newest_complete_cache();
    if (group->is_obsolete() || !cache)
        return false;

    AppCacheEntry* entry = cache->GetEntry(url);
    if (!entry || entry->IsForeign())
        return false;

    ScheduleSimpleTask(
        base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
            weak_factory_.GetWeakPtr(), url, *entry,
            make_scoped_refptr(group), make_scoped_refptr(cache),
            make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
    return true;
}

void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
    const GURL& url,
    const AppCacheEntry& found_entry,
    scoped_refptr<AppCacheGroup> group,
    scoped_refptr<AppCache> cache,
    scoped_refptr<DelegateReference> delegate_ref)
{
    if (delegate_ref->delegate) {
        DelegateReferenceVector delegates(1, delegate_ref);
        CallOnMainResponseFound(
            &delegates, url, found_entry,
            GURL(), AppCacheEntry(),
            cache.get() ? cache->cache_id() : kAppCacheNoCacheId,
            group.get() ? group->group_id() : kAppCacheNoCacheId,
            group.get() ? group->manifest_url() : GURL());
    }
}

void AppCacheStorageImpl::CallOnMainResponseFound(
    DelegateReferenceVector* delegates,
    const GURL& url,
    const AppCacheEntry& entry,
    const GURL& namespace_entry_url,
    const AppCacheEntry& fallback_entry,
    int64_t cache_id,
    int64_t group_id,
    const GURL& manifest_url)
{
    FOR_EACH_DELEGATE(
        (*delegates),
        OnMainResponseFound(url, entry,
            namespace_entry_url, fallback_entry,
            cache_id, group_id, manifest_url));
}

void AppCacheStorageImpl::FindResponseForSubRequest(
    AppCache* cache, const GURL& url,
    AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
    bool* found_network_namespace)
{
    DCHECK(cache && cache->is_complete());

    // When a group is forcibly deleted, all subresource loads for pages
    // using caches in the group will result in a synthesized network errors.
    // Forcible deletion is not a function that is covered by the HTML5 spec.
    if (cache->owning_group()->is_being_deleted()) {
        *found_entry = AppCacheEntry();
        *found_fallback_entry = AppCacheEntry();
        *found_network_namespace = false;
        return;
    }

    GURL fallback_namespace_not_used;
    GURL intercept_namespace_not_used;
    cache->FindResponseForRequest(
        url, found_entry, &intercept_namespace_not_used,
        found_fallback_entry, &fallback_namespace_not_used,
        found_network_namespace);
}

void AppCacheStorageImpl::MarkEntryAsForeign(const GURL& entry_url,
    int64_t cache_id)
{
    AppCache* cache = working_set_.GetCache(cache_id);
    if (cache) {
        AppCacheEntry* entry = cache->GetEntry(entry_url);
        DCHECK(entry);
        if (entry)
            entry->add_types(AppCacheEntry::FOREIGN);
    }
    scoped_refptr<MarkEntryAsForeignTask> task(
        new MarkEntryAsForeignTask(this, entry_url, cache_id));
    task->Schedule();
    pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
}

void AppCacheStorageImpl::MakeGroupObsolete(AppCacheGroup* group,
    Delegate* delegate,
    int response_code)
{
    DCHECK(group && delegate);
    scoped_refptr<MakeGroupObsoleteTask> task(
        new MakeGroupObsoleteTask(this, group, response_code));
    task->AddDelegate(GetOrCreateDelegateReference(delegate));
    task->Schedule();
}

void AppCacheStorageImpl::StoreEvictionTimes(AppCacheGroup* group)
{
    scoped_refptr<UpdateEvictionTimesTask> task(
        new UpdateEvictionTimesTask(this, group));
    task->Schedule();
}

AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
    const GURL& manifest_url,
    int64_t response_id)
{
    return new AppCacheResponseReader(
        response_id, is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}

AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
    const GURL& manifest_url)
{
    return new AppCacheResponseWriter(
        NewResponseId(), is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}

AppCacheResponseMetadataWriter*
AppCacheStorageImpl::CreateResponseMetadataWriter(int64_t response_id)
{
    return new AppCacheResponseMetadataWriter(
        response_id, is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
}

void AppCacheStorageImpl::DoomResponses(
    const GURL& manifest_url,
    const std::vector<int64_t>& response_ids)
{
    if (response_ids.empty())
        return;

    // Start deleting them from the disk cache lazily.
    StartDeletingResponses(response_ids);

    // Also schedule a database task to record these ids in the
    // deletable responses table.
    // TODO(michaeln): There is a race here. If the browser crashes
    // prior to committing these rows to the database and prior to us
    // having deleted them from the disk cache, we'll never delete them.
    scoped_refptr<InsertDeletableResponseIdsTask> task(
        new InsertDeletableResponseIdsTask(this));
    task->response_ids_ = response_ids;
    task->Schedule();
}

void AppCacheStorageImpl::DeleteResponses(
    const GURL& manifest_url,
    const std::vector<int64_t>& response_ids)
{
    if (response_ids.empty())
        return;
    StartDeletingResponses(response_ids);
}

void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses()
{
    // Only if we haven't already begun.
    if (!did_start_deleting_responses_) {
        scoped_refptr<GetDeletableResponseIdsTask> task(
            new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
        task->Schedule();
    }
}

void AppCacheStorageImpl::StartDeletingResponses(
    const std::vector<int64_t>& response_ids)
{
    DCHECK(!response_ids.empty());
    did_start_deleting_responses_ = true;
    deletable_response_ids_.insert(
        deletable_response_ids_.end(),
        response_ids.begin(), response_ids.end());
    if (!is_response_deletion_scheduled_)
        ScheduleDeleteOneResponse();
}

void AppCacheStorageImpl::ScheduleDeleteOneResponse()
{
    DCHECK(!is_response_deletion_scheduled_);
    const base::TimeDelta kBriefDelay = base::TimeDelta::FromMilliseconds(10);
    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE, base::Bind(&AppCacheStorageImpl::DeleteOneResponse, weak_factory_.GetWeakPtr()),
        kBriefDelay);
    is_response_deletion_scheduled_ = true;
}

void AppCacheStorageImpl::DeleteOneResponse()
{
    DCHECK(is_response_deletion_scheduled_);
    DCHECK(!deletable_response_ids_.empty());

    if (is_disabled_) {
        deletable_response_ids_.clear();
        deleted_response_ids_.clear();
        is_response_deletion_scheduled_ = false;
        return;
    }

    // TODO(michaeln): add group_id to DoomEntry args
    int64_t id = deletable_response_ids_.front();
    int rv = disk_cache()->DoomEntry(
        id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse, base::Unretained(this)));
    if (rv != net::ERR_IO_PENDING)
        OnDeletedOneResponse(rv);
}

void AppCacheStorageImpl::OnDeletedOneResponse(int rv)
{
    is_response_deletion_scheduled_ = false;
    if (is_disabled_)
        return;

    int64_t id = deletable_response_ids_.front();
    deletable_response_ids_.pop_front();
    if (rv != net::ERR_ABORTED)
        deleted_response_ids_.push_back(id);

    const size_t kBatchSize = 50U;
    if (deleted_response_ids_.size() >= kBatchSize || deletable_response_ids_.empty()) {
        scoped_refptr<DeleteDeletableResponseIdsTask> task(
            new DeleteDeletableResponseIdsTask(this));
        task->response_ids_.swap(deleted_response_ids_);
        task->Schedule();
    }

    if (deletable_response_ids_.empty()) {
        scoped_refptr<GetDeletableResponseIdsTask> task(
            new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
        task->Schedule();
        return;
    }

    ScheduleDeleteOneResponse();
}

AppCacheStorageImpl::CacheLoadTask*
AppCacheStorageImpl::GetPendingCacheLoadTask(int64_t cache_id)
{
    PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id);
    if (found != pending_cache_loads_.end())
        return found->second;
    return NULL;
}

AppCacheStorageImpl::GroupLoadTask*
AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url)
{
    PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url);
    if (found != pending_group_loads_.end())
        return found->second;
    return NULL;
}

void AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
    int64_t cache_id,
    std::vector<GURL>* urls)
{
    PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin();
    while (iter != pending_foreign_markings_.end()) {
        if (iter->second == cache_id)
            urls->push_back(iter->first);
        ++iter;
    }
}

void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task)
{
    pending_simple_tasks_.push_back(task);
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask, weak_factory_.GetWeakPtr()));
}

void AppCacheStorageImpl::RunOnePendingSimpleTask()
{
    DCHECK(!pending_simple_tasks_.empty());
    base::Closure task = pending_simple_tasks_.front();
    pending_simple_tasks_.pop_front();
    task.Run();
}

AppCacheDiskCache* AppCacheStorageImpl::disk_cache()
{
    DCHECK(IsInitTaskComplete());
    DCHECK(!is_disabled_);

    if (!disk_cache_) {
        int rv = net::OK;
        disk_cache_.reset(new AppCacheDiskCache);
        if (is_incognito_) {
            rv = disk_cache_->InitWithMemBackend(
                kMaxMemDiskCacheSize,
                base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
                    base::Unretained(this)));
        } else {
            rv = disk_cache_->InitWithDiskBackend(
                cache_directory_.Append(kDiskCacheDirectoryName),
                kMaxDiskCacheSize,
                false,
                cache_thread_.get(),
                base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
                    base::Unretained(this)));
        }

        if (rv != net::ERR_IO_PENDING)
            OnDiskCacheInitialized(rv);
    }
    return disk_cache_.get();
}

void AppCacheStorageImpl::OnDiskCacheInitialized(int rv)
{
    if (rv != net::OK) {
        LOG(ERROR) << "Failed to open the appcache diskcache.";
        AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR);

        // We're unable to open the disk cache, this is a fatal error that we can't
        // really recover from. We handle it by temporarily disabling the appcache
        // deleting the directory on disk and reinitializing the appcache system.
        Disable();
        if (rv != net::ERR_ABORTED)
            DeleteAndStartOver();
    }
}

void AppCacheStorageImpl::DeleteAndStartOver()
{
    DCHECK(is_disabled_);
    if (!is_incognito_) {
        VLOG(1) << "Deleting existing appcache data and starting over.";
        // We can have tasks in flight to close file handles on both the db
        // and cache threads, we need to allow those tasks to cycle thru
        // prior to deleting the files and calling reinit.
        cache_thread_->PostTaskAndReply(
            FROM_HERE,
            base::Bind(&base::DoNothing),
            base::Bind(&AppCacheStorageImpl::DeleteAndStartOverPart2,
                weak_factory_.GetWeakPtr()));
    }
}

void AppCacheStorageImpl::DeleteAndStartOverPart2()
{
    db_thread_->PostTaskAndReply(
        FROM_HERE,
        base::Bind(base::IgnoreResult(&base::DeleteFile), cache_directory_, true),
        base::Bind(&AppCacheStorageImpl::CallScheduleReinitialize,
            weak_factory_.GetWeakPtr()));
}

void AppCacheStorageImpl::CallScheduleReinitialize()
{
    service_->ScheduleReinitialize();
    // note: 'this' may be deleted at this point.
}

void AppCacheStorageImpl::LazilyCommitLastAccessTimes()
{
    if (lazy_commit_timer_.IsRunning())
        return;
    const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
    lazy_commit_timer_.Start(
        FROM_HERE, kDelay,
        base::Bind(&AppCacheStorageImpl::OnLazyCommitTimer,
            weak_factory_.GetWeakPtr()));
}

void AppCacheStorageImpl::OnLazyCommitTimer()
{
    lazy_commit_timer_.Stop();
    if (is_disabled())
        return;
    scoped_refptr<DatabaseTask> task(new CommitLastAccessTimesTask(this));
    task->Schedule();
}

} // namespace content
